package com.wx.infrastructure.config;

import com.alibaba.fastjson2.JSONException;
import com.wx.infrastructure.domain.ErrorCode;
import com.wx.infrastructure.domain.dto.ResultDTO;
import com.wx.infrastructure.exception.DefaultBizException;
import com.wx.infrastructure.exception.NoPrintStackAndStatus200BizException;
import com.wx.infrastructure.exception.NoPrintStackBizException;
import com.wx.infrastructure.exception.Status200BizException;
import com.wx.infrastructure.helper.RequestContextHelper;
import feign.RetryableException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.UnknownHostException;
import java.sql.SQLException;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * wx_back_end
 *
 * @Auther: cheng.tang
 * @Date: 2023/3/1 7:41 AM
 * @Description:
 */
@Slf4j
@RestControllerAdvice
public class GlobalApiExceptionHandler {

    @ExceptionHandler(Throwable.class)
    public ResultDTO<?> handlerThrowable(Throwable e) {
        return handleThrowable(e);
    }

    public ResultDTO<?> handleThrowable(Throwable e) {
        HttpServletResponse httpServletResponse = RequestContextHelper.buildResponseFacade();
        if (e instanceof DefaultBizException) {
            if (e instanceof Status200BizException) {
                httpServletResponse.setStatus(HttpStatus.OK.value());
            }

            if (e instanceof NoPrintStackBizException) {
                return handleNoPrintStackException((NoPrintStackBizException) e);
            } else if (e instanceof NoPrintStackAndStatus200BizException) {
                return handleNoPrintStackAndStatus200Exception((NoPrintStackAndStatus200BizException) e);
            } else {
                log.warn("handleThrowable {} {} msg {} {}", RequestContextHelper.buildLastAccessUrl(), e.getClass().getName(), e.getMessage(), e);
            }
            DefaultBizException defaultBizException = (DefaultBizException) e;
            ErrorCode bizError = defaultBizException.getErrorCode();
            if (Objects.equals(bizError.getCode(), HttpStatus.UNAUTHORIZED.value())) {
                httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
            }
            return ResultDTO.fail(bizError);
        }


        if (e instanceof MethodArgumentNotValidException) {
            log.warn(" handleThrowable {},msg:{}, {}", RequestContextHelper.buildLastAccessUrl(), e.getMessage(), e.getClass().getName());
            String errorMsg = ((MethodArgumentNotValidException) e).getBindingResult().getFieldErrors().stream()
                    .map(FieldError::getDefaultMessage)
                    .collect(Collectors.joining(System.lineSeparator()));
            return ResultDTO.fail(errorMsg);
        }

        if (e instanceof BindException) {
            log.warn(" handleThrowable {},msg:{}, {}", RequestContextHelper.buildLastAccessUrl(), e.getMessage(), e.getClass().getName());
            String errorMsg = ((BindException) e).getFieldErrors().stream()
                    .map(FieldError::getDefaultMessage)
                    .collect(Collectors.joining(System.lineSeparator()));
            return ResultDTO.fail(errorMsg);
        }


        if (e instanceof RetryableException || e instanceof UnknownHostException) {
            httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            String msg = e.getMessage();
            log.warn(" 当前服务不可用 {},msg:{}, {}", RequestContextHelper.buildLastAccessUrl(), msg, e.getClass().getName(), e);
            if (msg != null) {
                return ResultDTO.fail("当前服务" + msg + "不可用，请稍后再试");
            }
            return ResultDTO.fail("当前服务不可用，请稍后再试");
        }

        if (e instanceof SQLException || e instanceof IOException) {
            httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            log.error(" handleThrowable {},msg:{}, {}", RequestContextHelper.buildLastAccessUrl(), e.getMessage(), e.getClass().getName(), e);
            return ResultDTO.fail("出错了！");
        }
        if (e instanceof ServletRequestBindingException) {
            log.warn(" handleThrowable {},msg:{}, {}", RequestContextHelper.buildLastAccessUrl(), e.getMessage(), e.getClass().getName());
            String errorMsg = getServletRequestBindingExceptionMsg((ServletRequestBindingException) e);
            return ResultDTO.fail(errorMsg);
        }

        if (e instanceof JSONException) {
            log.warn(" handleThrowable {},msg:{}, {}", RequestContextHelper.buildLastAccessUrl(), e.getMessage(), e.getClass().getName(), e);
            String errorMsg = handleJsonException((JSONException) e);
            return ResultDTO.fail(errorMsg);
        }

        httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        log.error(" handleThrowable {} clazzName {} msg {}, {}", RequestContextHelper.buildLastAccessUrl(), e.getClass().getName(), e.getMessage(), e);
        return ResultDTO.fail(e.getMessage());
    }

    /**
     * 解决pinpoint中因为抛异常httpStatus=500造成告警过多的问题
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(Status200BizException.class)
    public ResultDTO<String> handleStatus200Exception(Status200BizException e) {
        log.warn("{} {} msg {}", RequestContextHelper.buildLastAccessUrl(), e.getClass().getName(), e.getMessage(), e);
        return ResultDTO.fail(e.getMessage());
    }


    @ExceptionHandler(NoPrintStackBizException.class)
    public ResultDTO<String> handleNoPrintStackException(NoPrintStackBizException e) {
        log.warn("{} {} msg {}", RequestContextHelper.buildLastAccessUrl(), e.getClass().getName(), e.getMessage());
        return ResultDTO.fail(e.getErrorCode());
    }


    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(NoPrintStackAndStatus200BizException.class)
    public ResultDTO<String> handleNoPrintStackAndStatus200Exception(NoPrintStackAndStatus200BizException e) {
        log.warn("{} {} msg {}", RequestContextHelper.buildLastAccessUrl(), e.getClass().getName(), e.getMessage());
        return ResultDTO.fail(e.getErrorCode());
    }


    private String getServletRequestBindingExceptionMsg(ServletRequestBindingException e) {
        String message = e.getMessage();
        if (message == null) {
            return "";
        }
        String[] split = message.split("'");
        if (split.length > 2) {
            return String.join(",", split[0], split[1]);
        }
        return message;
    }

    private String handleJsonException(JSONException e) {
        StringBuilder builder = new StringBuilder();
        if (e.getCause() != null) {
            builder.append(e.getCause().getMessage())
                    .append(System.lineSeparator());
        }
        builder.append(e.getMessage());
        return builder.toString();
    }

}
